#!/usr/bin/env python
from __future__ import print_function
#import string
import importlib
import argparse

p, allfuncs = None, None

def import_pack(package):
    print('[PackCLI] IMPORTING PACKAGE %s ...' % package)
    global p
    p = importlib.import_module(package)

def get_allfuncs():
    global p, allfuncs
    funcs_candidates = dir(p)
    allfuncs = []
    for f in funcs_candidates:
        ff = p.__getattribute__(f)
        if (not (f.startswith('__'))) and (hasattr(ff, 'func_code') or hasattr(ff, '__code__')):
            allfuncs.append(f)

def check_func(func):
    global p, allfuncs
    if hasattr(p, func):
        if allfuncs is None:
            ff = p.__getattribute__(func)
            if hasattr(ff, 'func_code') or hasattr(ff, '__code__'):
                return None
            get_allfuncs()
        else:
            if func in allfuncs:
                return None
        errorinfo = ['%s IS NOT A FUNCTION!'%func]
    else:
        if allfuncs is None:
            get_allfuncs()
        errorinfo = ['DO NOT FIND FUNCTION %s!' % func]

    errorinfo.append('AVAILABLES FUNCTIONS IN THE PACKAGE:')
    errorinfo.append(', '.join(allfuncs))
    raise IOError('\n  '.join(errorinfo))

def func_interface(func, convert=True, **kwds):
    print('[PackCLI] PREPARING ARGUMENTS ...')
    global p
    if convert:
        kwds2 = {}
        for k, v in kwds.items():
            if v is None:
                kwds2[k] = None
            elif isinstance(v, float) or isinstance(v, int):
                kwds2[k] = v
            elif isinstance(v, str):
                try:
                    kwds2[k] = int(v)
                except ValueError:
                    try:
                        kwds2[k] = float(v)
                    except ValueError:
                        kwds2[k] = v
            else:
                kwds2[k] = v
    else:
        kwds2 = kwds

    kwds2_string = []
    for k, v in kwds2.items():
        if isinstance(v, str):
            kwds2_string.append('%s="%s"' % (k, v))
        else:
            kwds2_string.append('%s=%s' % (k, v))

    print('[PackCLI] STARTING TO RUN %s(%s) ...' % (func, ', '.join(kwds2_string)))
    ret = p.__getattribute__(func)(**kwds2)
    if ret is None:
        print('All Done.')
    else:
        print('-'*25 + 'RETURNS' + '-'*25)
        print(ret)

def func_cli(func, input_kwdargs, convert=True):
    print('[PackCLI] CONSTRUCTING THE INTERFACE TO THE FUNCTION %s ...' % func)
    global p
    check_func(func)

    ff = p.__getattribute__(func)
    try:
        # py3
        name = ff.__name__
        fcode = ff.__code__
        default = ff.__defaults__
    except AttributeError:
        # py2
        name = ff.func_name
        fcode = ff.func_code
        default = ff.func_defaults

    doc = ff.__doc__
    nargs = fcode.co_argcount
    if nargs < 1:
        raise RuntimeError('The function does not have valid argument(s)!')
    args_vars = fcode.co_varnames[:nargs]
    if default is None:
        ndefault = 0
    else:
        ndefault = len(default)

    if doc is None:
        func_parser = argparse.ArgumentParser(prog='PackCLI.py '+p.__name__+' '+name, prefix_chars='-+')
    else:
        mydocs = ['----HERE ARE THE DOCUMENTS OF THE FUNCTION----']
        mydocs.extend([mydoc.strip() for mydoc in doc.strip().split('\n')])
      # mydocs.extend(map(string.strip, doc.strip().split('\n')))
        mydocs.append('-------------END OF THE DUCUMENTS-------------')
        func_description = '\n'.join(mydocs)
        func_parser = argparse.ArgumentParser(prog='PackCLI.py '+p.__name__+' '+name,
                                              description=func_description, prefix_chars='-+',
                                              formatter_class=argparse.RawDescriptionHelpFormatter)
    for iarg, var in enumerate(args_vars):
        kwargs = {}
        if iarg + ndefault >= nargs:
            _var = '-' + var
            def_val = default[iarg+ndefault-nargs]
            kwargs['default'] = def_val
            if (def_val is True) or (def_val is False):
                if def_val:
                    kwargs['action'] = 'store_false'
                else:
                    kwargs['action'] = 'store_true'
                    _var = '+' + _var[1:]
            elif isinstance(def_val, str) or (def_val is None):
                kwargs['type'] = str
            elif isinstance(def_val, int) or isinstance(def_val, float):
                kwargs['type'] = float
            else:
                kwargs['type'] = type(def_val)

            if ('action' in kwargs.keys()) and \
               ((kwargs['action'] == 'store_true') or (kwargs['action'] == 'store_false')):
                kwargs['help'] = "default: value=%s, type=<type 'bool'>" % kwargs['default']
            else:
                kwargs['help'] = "default: value=%s, type=%s" % (kwargs['default'], kwargs['type'])
        else:
            _var = var
        func_parser.add_argument(_var, **kwargs)
  # parser.add_argument('remainders', nargs=argparse.REMAINDER, help='remainder argmuments')

    args = func_parser.parse_args(input_kwdargs)
    func_interface(func, convert, **args.__dict__)

def cli():
    parser = argparse.ArgumentParser(description='This is the handler of functions in the package.')
    parser.add_argument('packName', type=str, help='the name of the package')
    parser.add_argument('func', type=str, help='the name of the function in the package')
    parser.add_argument('-nc', '--NotCovert', default=True, action='store_false', help='whether to convert input args')
    parser.add_argument('args', nargs=argparse.REMAINDER, help='remainding argmuments transmitted to the function')

    args = parser.parse_args()
    import_pack(args.packName)
    func_cli(args.func, args.args, convert=args.NotCovert)

if __name__ == '__main__': cli()
